#include <string.h> // 包含memset函数，但<string>不包含
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <signal.h>
#include <memory>
#include <libgen.h>  // 包含basename函数
#include "locker.h"
#include "threadpool.h"
#include "http_conn.h"
#define cout 0&&cout  // 禁用打印

using namespace std;

const int MAX_FD = 65535;  // 最大的文件描述符个数
const int MAX_EVENT_NUMBER = 10000;  // 监听的最大的事件数量

const string DB_USER="root";
const string DB_PASSWD = "123456";
const string DB_NAME = "webserverdb";


// 添加信号捕捉   handler为回调函数
void addsig(int sig, void(handler)(int)){
    cout << "正在添加捕捉信号:" << sig << endl;
    struct sigaction sa;  // 注册信号的参数
    memset(&sa, '\0', sizeof(sa));  // 将sa中的所有成员变量置'\0'
    sa.sa_handler = handler;
    int res = sigfillset(&sa.sa_mask); // 设置临时阻塞信号集,都为阻塞
    if(res==-1){
        cout << "sigfillset failure" << endl;
        exit(0);
    }
    res = sigaction(sig, &sa, nullptr);
    if(res==-1){
        cout << "sigaction failure" << endl;
        exit(0);
    }
}

// // 添加文件描述符到epoll中
// extern void addfd(int epollfd, int fd, bool one_short);
// // 从epoll中删除文件描述符
// extern void removefd(int epollfd, int fd);
// // 修改文件描述符
// extern void modfd(int epollfd, int fd, int ev);


int main(int argc, char* argv[]){
    if(argc<=1){ 
        // basename命令会删掉所有的前缀包括最后一个‘/’字符，然后将后缀的字符串显示出来
        cout << "按照如下格式运行："<<basename(argv[0])<<" port_number"<<endl;
        exit(-1);
    }
 
    // 获取端口号
    int port=atoi(argv[1]);

    // 忽略管道信号
    addsig(SIGPIPE, SIG_IGN);

    // 创建逻辑线程池，初始化线程池
    shared_ptr<threadpool<http_conn>> logic_thread_pool=nullptr;
    try{
        logic_thread_pool = make_shared<threadpool<http_conn>>(2);
        // shared_ptr<threadpool<http_conn>> pool = make_shared<threadpool<http_conn>>();
    }catch(...){
        exit(-1);
    }

    // 创建IO线程池，初始化线程池
    shared_ptr<threadpool<http_conn>> io_thread_pool=nullptr;
    try{
        io_thread_pool = make_shared<threadpool<http_conn>>(1, 4);
        // shared_ptr<threadpool<http_conn>> pool = make_shared<threadpool<http_conn>>();
    }catch(...){
        exit(-1);
    }

    // 初始化数据库连接池
    connection_pool* conn_pool=nullptr;
    try{
        conn_pool = connection_pool::GetInstance();
        conn_pool->init("localhost", DB_USER, DB_PASSWD, DB_NAME, 3306, 8);
    }catch(...){
        exit(-1);
    }
    http_conn::m_p_conn_pool = conn_pool;
    http_conn::m_logic_thread_pool = logic_thread_pool;
    // static shared_ptr<threadpool<http_conn>> m_logic_thread_pool= logic_thread_pool;

    // 用于保存所有的客户端信息
    vector<http_conn> users(MAX_FD);
    // 创建socket套接字，PF_INET：使用IPv4网络协议，SOCK_STREAM：提供有序的、可靠的、双向的、基于连接的字节流
    int listenfd = socket(PF_INET, SOCK_STREAM, 0); 
    if(listenfd == -1){
        perror("socket");
        exit(-1);
    }

    // 设置端口复用：防止服务器重启时之前绑定的端口还未释放；程序突然退出而系统没有释放端口
    // setsockopt：设置套接字的属性(不仅仅能设置端口复用)
    int reuse = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

    // 绑定
    struct sockaddr_in address;
    address.sin_family=AF_INET;     // 协议族AF_INET=PF_INET
    address.sin_addr.s_addr = INADDR_ANY;  // 0.0.0.0任意地址
    address.sin_port= htons(port);  // htons将端口转换为网络字节序
    int res = bind(listenfd, (sockaddr*)&address, sizeof(address));
    if(res == -1){
        perror("bind");
        exit(-1);
    }

    // 监听 创建监听队列以存放待处理的客户连接，在这些客户连接被accept()之前
    res = listen(listenfd, 5);
    if(res == -1){
        perror("listen");
        exit(-1);
    }

    // 创建epoll对象，
    epoll_event events[MAX_EVENT_NUMBER];
    int epollfd = epoll_create(5);

    // 将监听的文件描述符添加到epoll中
    addfd(epollfd, listenfd, false);  // 监听的文件描述符一般是水平触发，因为只有主线程处理它，所以不需要EPOLLONESHOT
    http_conn::m_epollfd = epollfd;

    while(true){
        // 调用epoll_wait等待一组文件描述符上的事件，并将当前所有就绪的epoll_event复制到events数组中
        int num = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
        cout << "epoll num: " << num << endl;
        if(num<0&&errno!=EINTR){ // 若调用失败且不是被中断EINTR
            cout << "epoll failure" << endl;
            break;
        }

        // 循环遍历事件数组
        for(int i=0;i<num;++i){
            int sockfd= events[i].data.fd;
            if(sockfd==listenfd){ // 当有新客户端连接进来,listenfd上则产生就绪事件
                cout << "main: 新连接" << endl;
                struct sockaddr_in client_address;
                socklen_t client_addrlen=sizeof(client_address);
                // accept()返回一个新的socket文件描述符用于send()和recv()
                int connfd=accept(listenfd, (struct sockaddr*)&client_address, &client_addrlen);
                if (connfd < 0)
                {
                    cout << "accept error" << errno;
                    return false;
                }
                if(http_conn::m_user_count>=MAX_FD){
                    // 目前连接数满了
                    // 给客户端写一个信息：服务器内部正忙.........................
                    cout << "目前连接数满了" << endl;
                    close(connfd);
                    continue;
                }
                // 将新的客户的数据初始化，放到数组中,并将connfd注册到内核事件表中
                users[connfd].init(connfd, client_address);
            }else if(events[i].events&(EPOLLRDHUP|EPOLLHUP|EPOLLERR)){
                cout << "main: 对方异常断开或错误等事件" << endl;
                // 对方异常断开或错误等事件
                users[sockfd].close_conn();
            }else if(events[i].events&EPOLLIN){  // 读事件
                cout << "main: 读事件" << endl;
                users[sockfd].is_need_read=true;
                io_thread_pool->append(&users[sockfd]);
            }else if(events[i].events&EPOLLOUT){  // 写事件
                cout << "main: 写事件" << endl;
                users[sockfd].is_need_read=false;
                io_thread_pool->append(&users[sockfd]);
            }
        }
    }

    close(epollfd);
    close(listenfd);
    
    return 0;
}



/* 
    #include <signal.h>
    typedef void (*sighandler_t)(int);
    sighandler_t signal(int signum, sighandler_t handler);
        - 功能：设置某个信号的捕捉行为
        - 参数：
            - signum：要捕捉的信号
            - handler：捕捉到信号要如何处理
                - SIG_IGN：忽略信号
                - SIG_DFL：使用信号默认的行为
                - 回调函数：这个函数由内核调用，程序员只负责写捕捉到信号后如何去处理信号
        - 返回值：
            成功，返回上一次注册的信号处理函数的地址。第一次调用返回NULL
            失败，返回SIG_ERR，设置错误号
    注意：SIGKILL SIGSTOP不能被捕捉，不能被忽略

    以下函数只能对自定义的信号集进行操作
    int sigemptyset(sigset_t* set) 将信号集中所有标志置为0，表示不阻塞；成功返回0，失败返回-1
    int sigfillset(sigset_t* set) 将信号集中所有标志置为1，表示阻塞；成功返回0，失败返回-1
    int sigaddset(sigset_t* set, int signum) 设置信号集中某一个信号对应的标志位为1，表示阻塞
    int sigdelset(sigset_t* set, int signum) 设置信号集中某一个信号对应的标志位为0，表示不阻塞
    int sigismember(sigset_t* set, int signum) 判断某个信号是否阻塞，1:被阻塞 0:不被阻塞 -1:错误

    以下函数可以对内核信号集进行操作
    int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
        - 功能：将自定义信号集中的数据设置到内核中（设置阻塞，解除阻塞，替换）
        - 参数：
            - how：如何对内核阻塞信号集进行处理
                SIG_BLOCK：将用户设置的阻塞信号集添加到内核中   mask |= set
                SIG_UNBLOCK：根据用户设置的数据，对内核中的数据进行解除阻塞 mask &= ~set
                SIG_SETMASK：覆盖内核中原来的值
            
            - set：用户自定义的信号集
            - oldset：保存设置之前的内核中的阻塞信号集的状态，可以为NULL
        - 返回值：
            成功：0
            失败：-1 并设置错误号：EFAULT，RINVAL
    int sigpending(sigset_t *set) 获取内核总的未决信号集

    int sigacton(int signum, const struct sigaction *act, 
            struct sigaction *oldact)
        - 功能：检查或改变信号的处理。信号捕捉
        - 参数：
            - signum：要捕捉的信号的编号或宏值（推荐宏值，可跨平台）
            - act：捕捉后的处理动作
            - oldact：上一次对信号捕捉相关的设置，可以为NULL
        - 返回值：
            成功：0
            失败：-1
    struct sigaction{
        void (*sa_handler)(int); // 函数指针，指向信号捕捉后的处理函数
        void (*sa_sigaction)(int, siginfo_t, void*);    // 不常用
        sigset_t sa_mask;   // 临时阻塞信号集，在信号处理函数执行过程中临时阻塞某些信号
        int  sa_flags;  // 使用哪一个信号处理对捕捉到的信号进行处理（0：sa_handler或SA_SIGINFO：sa_sigaction）
        void (*sa_restorer)(void); // 被废弃掉了
    }


*/
/* 
    #include<socket.h>

    int setsockopt(int sockfd, int level, int optname, const void* optval, socklen_t optlen);
        参数：
            - sockfd：要操作的文件描述符
            - level：级别，SOL_SOCKET 端口复用的级别
            - optname：选项的名称 SO_REUSEADDR   SO_REUSEPORT
            - optval：端口复用的值（整形） 1可以复用   0不可以复用
            - optlen：optval参数的大小
        - 注：端口复用，设置时机是在服务器绑定端口之前

    int socket(int domain, int type, int protocol)
    - 功能：创建一个套接字
    - 参数：
        - domain：协议族
            AF_INET:ipv4
            AF_INET6:ipv6
            AF_UNIX,AF_LOCAL:本地套接字通信
        - type：通信过程中使用的协议类型
            SOCK_STREAM：流式协议
            SOCK_DGRAM：报式协议
        - protocol：具体的一个协议。一般写0
            SOCK_STREAM默认使用TCP，SOCK_DGRAM默认使用UDP
    - 返回值：
        成功：返回文件描述符，操作的就是内核缓冲区
        失败：-1
    
    int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen)
        - 功能：将fd和本地的IP+端口进行绑定
        - 参数：
            - sockfd：文件描述符
            - addr：需要绑定的客户端的socket地址，这个地址封装了ip和端口号信息
            - addrlen：第二个参数结构体占的内存大小 

    int listen(int sockfd, int backlog)
        - 功能：监听这个socket上的连接
        - 参数：
            - sockfd：文件描述符
            - backlog：准备连接的客户端的最大值，5，不可超过4096

    int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen)
        - 功能：接收客户端连接，默认是一个阻塞的函数，阻塞等待客户端连接
        - 参数：
            - sockfd：服务端用于监听的文件描述符
            - addr：传出参数，记录连接成功后客户端的地址信息(ip,port)
            - addrlen：第二个参数结构体占的内存大小，需要传地址
        - 返回值：
            成功：用于通信的文件描述符
            失败：-1 

    int connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen)
        - 功能：客户端连接服务器
        - 参数：
            - sockfd：客户端用于通信的文件描述符
            - addr：需要绑定的服务器的socket地址
            - addrlen：第二个参数结构体占的内存大小
        - 返回值：
            成功：0
            失败：-1

    ssize_t write(int fd, const void* buf, size_t count)  // 写数据
    ssize_t read(int fd, void* buf, size_t count)  // 读数据


*/